//=============================================================================

#include <map>
#include <algorithm>

#include "texture.hpp"
#include "lodepng.h"
#include "helper.hpp"
#include "log.hpp"
#include "global.hpp"

using namespace std;

namespace KEG
{
	static map< std::string, Texture > loadedTex;
	static map< GLuint, std::string > loadedName;
	typedef map< string, Texture>::iterator mapTexItr;
	typedef map< GLuint, std::string >::iterator mapNameItr;

	void Texture::DestroyMagentaAndSetFlags( std::vector< unsigned char >& image )
	{
		for( int i = 0; i < image.size() - 4; i += 4 )
		{
			if( 255 == image[i] && 255 == image[i+2] && 0 == image[i+1] )
			{
				image[i+3] = 0; // turn alpha off on magenta
			}
			//LOG_RAW() << i << ' ' << (int)image[i] << (int)image[i+1] << (int)image[i+2] << (int)image[i+3] << std::endl;
			if( image[i+3] == 0 )
			{
				Alpha = 1;
			}
			else if( image[i+3] < 255 )
			{
				Blend = 1;
			}
		}
	}

	void Texture::GrowNextPow2( std::vector< unsigned char >& image )
	{
		int newWidth = NextPow2( Width );
		int newHeight = NextPow2( Height );

		// MAGIC NUMBER 4=bpp
		vector< unsigned char > newImage( newWidth*newHeight*4, 0 );

		// copy over the image data into larger image
		for( int y=0; y < Height; ++y )
		{
			// copy a row
			copy( image.begin() + y*Width*4 , image.begin() + (y+1)*Width*4, &newImage[y*newWidth*4] );
		}

		// set new info
		image.swap( newImage );
		Width = newWidth;
		Height = newHeight;

	}

	bool Texture::Create( const std::string& filename )
	{
		//if(filter) LOG_RAW() << "Loading Texture: " << filename << " with filtering" << std::endl;
		//else LOG_RAW() << "Loading Texture: " << filename << " with no filtering" << std::endl;
		LOG_RAW() << "Loading Texture: " << filename << std::endl;
		
		ifstream file( filename.c_str(), ios::in|ios::binary|ios::ate);

		if( !file )
		{
			LOG_ERROR( "Couldn't load texture from path (follows):" );
			LOG_RAW() << filename << std::endl;
			return false;
		}

		// read in the whole file
		file.seekg(0, ios::end);
		int size = file.tellg(); 
		file.seekg(0, ios::beg);
		size -= file.tellg();

		//read contents of the file into the vector named in
		std::vector<unsigned char> in;
		in.resize(size);
		file.read((char*)(&in[0]), size);
		file.close();
		
		// now call lodepng
		vector< unsigned char > image;
		//vector< int > info;
		unsigned int imageWidth, imageHeight;
		int error;

		error = LodePNG::decode(image, imageWidth, imageHeight, in);

		if( error < 0 )
		{
			LOG_ERROR( "PNG Loading Error!" );
			LOG_RAW() << filename << " errno: " << error;
			return false;
		}

		Width = imageWidth;
		Height = imageHeight;

		// get info and polish the image
		DestroyMagentaAndSetFlags( image );

		// various load methods based on hardware caps
#pragma
		LOG_INFO( "Loading Texture as Texture Rect" );

		glGenTextures( 1, &Tex );
		glBindTexture( GL_TEXTURE_RECTANGLE_ARB, Tex );
		glTexParameteri( GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MIN_FILTER, GL_NEAREST );
		glTexParameteri( GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MAG_FILTER, GL_NEAREST );
		glTexParameteri( GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_WRAP_S, GL_CLAMP );
		glTexParameteri( GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_WRAP_T, GL_CLAMP );
		glTexImage2D( GL_TEXTURE_RECTANGLE_ARB, 0, GL_RGBA, Width, Height, 0, GL_RGBA, GL_UNSIGNED_BYTE, &image[0] );

#ifdef PATHWAY
		if( Global::OGLCaps::NonPow2 )
		{

		LOG_INFO( "Loading Texture As Non_Pow_2" );

		glGenTextures( 1, &texInfo.tex );
		glBindTexture( GL_TEXTURE_2D, texInfo.tex );
		glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filter?GL_LINEAR:GL_NEAREST );
		glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filter?GL_LINEAR:GL_NEAREST );
		glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA, texInfo.width, texInfo.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, &image[0] );

		}
		else if( Global::OGLCaps::TexRect )
		{

		LOG_INFO( "Loading Texture as Texture Rect" );

		glGenTextures( 1, &texInfo.tex );
		glBindTexture( GL_TEXTURE_RECTANGLE_ARB, texInfo.tex );
		glTexParameteri( GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MIN_FILTER, filter?GL_LINEAR:GL_NEAREST );
		glTexParameteri( GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MAG_FILTER, filter?GL_LINEAR:GL_NEAREST );
		glTexImage2D( GL_TEXTURE_RECTANGLE_ARB, 0, GL_RGBA, texInfo.width, texInfo.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, &image[0] );

		}
		else
		{

		LOG_INFO( "Damn, old video card. Padding texture to Pow_of_2" );

		GrowNextPow2( image, texInfo );

		glGenTextures( 1, &texInfo.tex );
		glBindTexture( GL_TEXTURE_2D, texInfo.tex );
		glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filter?GL_LINEAR:GL_NEAREST );
		glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filter?GL_LINEAR:GL_NEAREST );
		glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA, texInfo.width, texInfo.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, &image[0] );
		
		}
#endif

		assert( glGetError() == NO_ERROR );

		return true;
	}

	bool Texture::Load( const std::string& filename )
	{
		mapTexItr foundItr = loadedTex.find( filename );
		if( foundItr != loadedTex.end() )
		{
			//texInfo.tex = foundItr->second.tex;
			//texInfo.width = foundItr->second.width;
			//texInfo.height = foundItr->second.height;
			*this = foundItr->second;
			return true;
		}

		bool rc = Create( filename );

		// add into map
		loadedTex[ filename ] = *this;
		loadedName[ Tex ] = filename;

		return rc;
	}

	void Texture::Release()
	{
		// only safe if there is one copy!
		LOG_RAW() << "Single Texture Delete: " << loadedName[Tex] << " with ID " << Tex << std::endl;
		loadedTex.erase( loadedName[Tex] );
		loadedName.erase( Tex );
		glDeleteTextures( 1, &Tex );
		Tex = Width = Height = Alpha = Blend = 0;
	}

	void Texture::ReleaseAll()
	{
		for( mapNameItr itr = loadedName.begin();
			itr != loadedName.end();
			++itr )
		{
			LOG_RAW() << "Routine Texture Delete: " << itr->second << " with ID " << itr->first << std::endl;
			glDeleteTextures( 1, &(itr->first) );
		}
		loadedTex.clear();
		loadedName.clear();
	}

	//bool Texture::DuplicateTex( const std::string& filename, GLuint& tex, int& width, int& height )
	//{
	//	return CreateTex( filename, tex, width, height );
	//}

}
